//
//  CLReporting.m
//  Corellis_iPhone_Analytics_Reporting
//
//  Created by Antoine Poupel on 04/05/09.
//  Copyright 2009 Corellis, Applications et Innovations NTIC. All rights reserved.
//

#import "CLReporting.h"

#include <execinfo.h>

#define MIME_BOUNDARY "----16c17a9ea1d7b327e7489190e394d411----"
#define CONTENT_TYPE "multipart/form-data; boundary=" MIME_BOUNDARY

@interface CLReporting (Private)
- (void)_pushData:(NSString*) commandString;
- (void)_pushDataThread:(NSString*) commandString;
- (void)_postData:(NSDictionary*) params;
- (void)_postDataThread:(NSDictionary*) params;
-(void)_initDevice;
-(void)_updateDevice;
- (NSString *)decodedExceptionStackTrace:(NSException *)exception;
- (NSString *)decodedStackTraceFromReturnAddresses:(NSException *)exception;
@end

void exceptionHandler(NSException *exception) {
	NSLog(@"%@", [[CLReporting sharedInstance] decodedExceptionStackTrace:exception]);
	[[CLReporting sharedInstance] reportException:exception];
}


// This is a singleton class, see below
static CLReporting *_sharedInstance = nil;

@implementation CLReporting

@synthesize uniqueIdentifier, appKey, currentLocale, currentSystemName, currentSystemVersion, currentModel, bundleVersion;

+ (CLReporting *)sharedInstance
{
    if (_sharedInstance == nil) {
		_sharedInstance = [[[CLReporting alloc] init] retain];
    }
    return _sharedInstance;
}

- (void)registerApp:(NSString*) anAppKey
{
	appKey = anAppKey;
	[self _initDevice];	
	[self _pushData:[NSString stringWithFormat:@"registerApp/%@/", anAppKey]];
}

- (void)startSession
{
	[self _updateDevice];
	[self _pushData:[NSString stringWithFormat:@"startSession/%@/%@/%@/%@/%@/%@/%@/%d/", appKey, bundleVersion, uniqueIdentifier, currentLocale, currentSystemName, currentSystemVersion, currentModel, currentOrientation]];
}

- (void)endSession
{
	// synchro with main thread because call are done in applicationWillTerminate
	[self _pushDataThread:[NSString stringWithFormat:@"endSession/%@/%@/", appKey, uniqueIdentifier]];
}

- (void)reportMemoryWarning
{
	[self _pushData:[NSString stringWithFormat:@"addMemoryWarning/%@/%@/%@/%@/%@/%@/%@/%d/", appKey, bundleVersion, uniqueIdentifier, currentLocale, currentSystemName, currentSystemVersion, currentModel, currentOrientation]];
}

- (void)reportTag:(NSString*) aTag
{
	[self _pushData:[NSString stringWithFormat:@"addTag/%@/%@/%@/%@", appKey, bundleVersion, uniqueIdentifier, aTag]];	
}

- (void)reportUserInput:(NSString*) anUserInput
{
	[self _pushData:[NSString stringWithFormat:@"addUserInput/%@/%@/%@/%@", appKey, bundleVersion, uniqueIdentifier, anUserInput]];		
}

- (void)reportUserLocation:(CLLocation *)alocation
{
	if( alocation==nil)
		return;
	
	// Reverse geocode, detecte nearest city -> send city coordinate
	MKReverseGeocoder *reverse = [[MKReverseGeocoder alloc] initWithCoordinate:alocation.coordinate];
	[reverse setDelegate:self];
	[reverse start];
	
}

- (void)reportDeviceOrientation
{
	[self _updateDevice];
	[self _pushData:[NSString stringWithFormat:@"addDeviceOrientation/%@/%@/%@/%d", appKey, bundleVersion, uniqueIdentifier, currentOrientation]];		
}

- (void)reportException:(NSException*)exception
{
	@try {
		NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithObjectsAndKeys: 
		     [[NSString alloc] initWithFormat:@"addException/%@/%@/%@/%@/%@/%@/%@/%@/%@/%d/", appKey, bundleVersion,  uniqueIdentifier, [exception name], [exception description], currentLocale, currentSystemName, currentSystemVersion, currentModel, currentOrientation], @"url", 
			 [self decodedExceptionStackTrace:exception], @"stacktrace", nil];	
		[self _postDataThread:params];
	}@catch (NSException * e) {
		// ignore this one
	}
}

- (void)openURL:(NSURL *)url
{
	[self _pushData:[NSString stringWithFormat:@"addURL/%@/%@/%@/%@", appKey, bundleVersion, uniqueIdentifier, [url absoluteString]]];	
}


-(void)_initDevice
{
	NSSetUncaughtExceptionHandler(&exceptionHandler);

	NSDictionary* infoDic = [[NSBundle mainBundle] infoDictionary];
	self.bundleVersion = [infoDic objectForKey:@"CFBundleVersion"];
	self.uniqueIdentifier = [NSString stringWithString:[[UIDevice currentDevice] uniqueIdentifier]];
	self.currentLocale = [[NSLocale currentLocale] localeIdentifier];
	self.currentSystemName = [NSString stringWithString:[[UIDevice currentDevice] systemName]];
	self.currentSystemVersion =  [NSString stringWithString:[[UIDevice currentDevice] systemVersion]];
	self.currentModel =  [NSString stringWithString:[[UIDevice currentDevice] model]];
	[self _updateDevice];
}

-(void)_updateDevice
{
	currentOrientation =  [[UIDevice currentDevice] orientation];	
	
	/* v3.0 */
	//currentBatteryLevel =  [[UIDevice currentDevice] batteryLevel];
	//currentBatteryState =  [[UIDevice currentDevice] batteryState];
}

- (void)_pushData:(NSString*) commandString
{
	[NSThread detachNewThreadSelector:@selector(_pushDataThread:) toTarget:self withObject:commandString];
}

-(void)_pushDataThread:(NSString*) commandString
{
	@try {
		
		NSAutoreleasePool *subPool = [[NSAutoreleasePool alloc] init];
		
		NSString *urlString =  [[NSString stringWithFormat:
								 @"%@/%@", 
								 kCiReportServiceURL,
								 commandString]
								stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
		NSLog(@"CIReports reports with URL : %@", urlString);
		NSURL *url = [[NSURL alloc] initWithString:urlString];
		NSString *returnString = [[NSString alloc] initWithContentsOfURL:url];	
		/*if (returnString != nil 
					&& [returnString length] >0) 
		{
			NSLog(@"return string: %@", returnString);
		}	*/
		[returnString release];
		[url release];
		
		[subPool release];
	}
	@catch (NSException * e) {
		// ignore this one	
	}
}

- (void)_postData:(NSDictionary*) params
{
	[NSThread detachNewThreadSelector:@selector(_postDataThread:) toTarget:self withObject:params];
}


- (void)_postDataThread:(NSDictionary*) params
{	
	@try {	
		NSAutoreleasePool *subPool = [[NSAutoreleasePool alloc] init];
		
		NSString *urlString =  [[NSString stringWithFormat:@"%@/%@", 
								 kCiReportServiceURL,
								 [params objectForKey:@"url"]] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
		
		NSMutableDictionary* paramsCopy = [NSMutableDictionary dictionaryWithDictionary:params];	
		[paramsCopy removeObjectForKey:@"url"];	
		
		NSMutableData *body = [[NSMutableData alloc] initWithLength: 0];
		[body appendData: [[[[NSString alloc] initWithFormat: @"--%@\r\n", @MIME_BOUNDARY] autorelease] dataUsingEncoding: NSUTF8StringEncoding]];
		
		id key = nil;
		NSEnumerator *enumerator = [paramsCopy keyEnumerator];
		while(key = [enumerator nextObject]) {
			id val = [paramsCopy objectForKey: key];
			id keyHeader = nil;
			
			keyHeader = [NSString stringWithFormat: @"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key];
			[body appendData: [keyHeader dataUsingEncoding: NSUTF8StringEncoding]];
			[body appendData: [val dataUsingEncoding: NSUTF8StringEncoding]];
			[body appendData: [[NSString stringWithFormat: @"\r\n--%@\r\n", @MIME_BOUNDARY] dataUsingEncoding: NSUTF8StringEncoding]];
		}	
		
		[body appendData: [[NSString stringWithString: @"--\r\n"] dataUsingEncoding: NSUTF8StringEncoding]];
		long bodyLength = [body length];
		
		CFURLRef uploadURL = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef) urlString, NULL);
		CFHTTPMessageRef _request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("POST"), uploadURL, kCFHTTPVersion1_1);
		CFRelease(uploadURL);
		uploadURL = NULL;
		
		CFHTTPMessageSetHeaderFieldValue(_request, CFSTR("Content-Type"), CFSTR(CONTENT_TYPE));
		CFHTTPMessageSetHeaderFieldValue(_request, CFSTR("Host"), (CFStringRef) kCiReportServiceHost);
		CFHTTPMessageSetHeaderFieldValue(_request, CFSTR("Content-Length"), (CFStringRef)[NSString stringWithFormat: @"%d", bodyLength]);
		CFHTTPMessageSetBody(_request, (CFDataRef)body);
		[body release];
		
		CFReadStreamRef _readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, _request);
		CFReadStreamOpen(_readStream);
		
		CFIndex numBytesRead;
		long bytesWritten, previousBytesWritten = 0;
		UInt8 buf[1024];
		BOOL doneUploading = NO;
		
		while (!doneUploading) {
			CFNumberRef cfSize = CFReadStreamCopyProperty(_readStream, kCFStreamPropertyHTTPRequestBytesWrittenCount);
			CFNumberGetValue(cfSize, kCFNumberLongType, &bytesWritten);
			CFRelease(cfSize);
			cfSize = NULL;
			if (bytesWritten > previousBytesWritten) {
				previousBytesWritten = bytesWritten;
			}
			
			if (!CFReadStreamHasBytesAvailable(_readStream)) {
				usleep(3200);
				continue;
			}
			
			numBytesRead = CFReadStreamRead(_readStream, buf, 1024);
			fprintf(stderr, "%s", buf);
			fflush(stderr);
			
			if (CFReadStreamGetStatus(_readStream) == kCFStreamStatusAtEnd) doneUploading = YES;
		}
		CFHTTPMessageRef _responseHeaderRef = (CFHTTPMessageRef)CFReadStreamCopyProperty(_readStream, kCFStreamPropertyHTTPResponseHeader);
		CFRelease(_responseHeaderRef);
		_responseHeaderRef = NULL;
		
		CFReadStreamClose(_readStream);
		CFRelease(_request);
		_request = NULL;
		CFRelease(_readStream);
		_readStream = NULL;
		
		[subPool release];
	}
	@catch (NSException * e) {
		// ignore this one		
	}
	return;
}



-(void) dealloc
{
	[bundleVersion release];	
	[uniqueIdentifier release];	
	[appKey release];
	[currentLocale release];
	[currentSystemName release];
	[currentSystemVersion release];
	[currentModel release];
	[super dealloc];
}

- (NSString *)decodedExceptionStackTrace:(NSException *)exception {
	NSString *trace = nil;
	if (!trace) { trace = [self decodedStackTraceFromReturnAddresses:exception]; }
	return trace;
}

- (NSString *)decodedStackTraceFromReturnAddresses:(NSException *)exception {
	
	int i = 0;
	int count = [[exception callStackReturnAddresses] count];
	void *frames[count];
	
	// build frame pointers
	NSEnumerator *addresses = [[exception callStackReturnAddresses] objectEnumerator];
	NSNumber *address;
	while (address = [addresses nextObject]) {
		frames[i++] = (void *)[address unsignedIntegerValue];
	}
	
	// Get symbols for the backtrace addresses
	char **frameStrings = backtrace_symbols(frames, count);
	
	NSMutableString *backtrace = [NSMutableString string];
	
	if (frameStrings) {
		for(i = 0; i < count; i++) {
			if(frameStrings[i]) {
				[backtrace appendFormat:@"%s\n", frameStrings[i]];
			}
		}
	}
	
	return backtrace;
}

#pragma mark -
#pragma mark MKReverseGeocoderDelegate
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark
{
	[self _pushData:[NSString stringWithFormat:@"addUserLocation/%@/%@/%@/%lf/%lf/%@/%@", appKey, bundleVersion, uniqueIdentifier, geocoder.coordinate.latitude, geocoder.coordinate.longitude, placemark.locality, placemark.countryCode]];	
}

- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error
{
	[self _pushData:[NSString stringWithFormat:@"addUserLocation/%@/%@/%@/%lf/%lf/%@/%@", appKey, bundleVersion, uniqueIdentifier, geocoder.coordinate.latitude, geocoder.coordinate.longitude, @"Unknown locality", @"Unknown country"]];	
}

#pragma mark -


@end
